package net.chery.exec;

import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.RowMapper;

import java.sql.*;
import java.util.*;
import java.util.function.Consumer;

/**
 * @author gai
 */
@Data
@Slf4j
public class BaseExec implements InitializingBean, DisposableBean {

    private String driverClassName;
    private String url;
    private String username;
    private String password;

    protected HikariDataSource dataSource;
    protected long initTime;

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }

    protected void init() throws Exception{
        dataSource= DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .driverClassName(driverClassName)
                .url(url)
                .username(username)
                .password(password)
                .build();
        dataSource.setMaximumPoolSize(10);
        dataSource.setMinimumIdle(1);
        dataSource.setConnectionTimeout(600000);
        dataSource.setMaxLifetime(600000);
        dataSource.setIdleTimeout(600000);
        initTime=System.currentTimeMillis();
    }

    public boolean isActive() {

        HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean();
        // 检查活跃连接
        return poolMXBean!=null&&poolMXBean.getActiveConnections()>0;
    }

    @Override
    public void destroy() {
        dataSource.close();
    }

    public void exec(String sql, Consumer<ResultSet> consumer, Object ...params){
        execEntity(sql,null,consumer,params);
    }

    public Connection getConnection(){
        try {
            initTime=System.currentTimeMillis();
            if(dataSource.isClosed()){
                init();
            }
            return dataSource.getConnection();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void execMeta(String sql, Object ...params){
        try (Connection connection=getConnection();
             PreparedStatement statement=connection.prepareStatement(sql)){
            if(params!=null&&params.length>0){
                for (int i = 0; i < params.length; i++) {
                    statement.setObject(i+1,params[i]);
                }
            }
            try (ResultSet rs=statement.executeQuery()){
                List<String> meta = getMeta(rs);
                log.info("sql:{},meta:{}",sql,meta);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private static List<String> getMeta(ResultSet rs) throws SQLException {
        ResultSetMetaData metaData= rs.getMetaData();
        int size=metaData.getColumnCount();
        List<String> meta=new ArrayList<>(size);
        for (int i = 1; i <= size; i++) {
            meta.add(String.format("{columnName:\"%s\",columnTypeName:\"%s\",columnClassName:\"%s\"}",
                    metaData.getColumnName(i),
                    metaData.getColumnTypeName(i),
                    metaData.getColumnClassName(i)));
        }
        return meta;
    }

    public Map<String,Object> selectMap(String sql, Object ...params){
        List<Map<String,Object>> res = select(sql,params);
        if(!res.isEmpty()){
            return res.get(0);
        }
        return Collections.emptyMap();
    }

    public List<Map<String,Object>> select(String sql, Object ...params){
        List<Map<String,Object>> res=new ArrayList<>();
        RowMapper<Map<String,Object>> rowMapper=new ColumnMapRowMapper();
        execEntity(sql,rowMapper, res::add,params);
        return res;
    }

    public <T>void execEntity(String sql, RowMapper<T> rowMapper, Consumer<T> consumer, Object ...params){
        try (Connection connection=getConnection();
             PreparedStatement statement=connection.prepareStatement(sql)){
            if(params!=null&&params.length>0){
                for (int i = 0; i < params.length; i++) {
                    Object p=params[i];
                    if((p instanceof java.util.Date dp)){
                        statement.setDate(i+1,new java.sql.Date(dp.getTime()));
                    } else if (p instanceof String sp){
                        statement.setString(i+1,sp);
                    } else {
                        statement.setObject(i+1,p);
                    }
                }
            }
//            log.info("fetch data by sql:{}",sql);
            try (ResultSet rs=statement.executeQuery()){
                int rowNum=0;
                while (rs.next()) {
                    if(rowMapper!=null){
                        consumer.accept(rowMapper.mapRow(rs,rowNum++));
                    } else {
                        consumer.accept((T) rs);
                    }
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
